{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-26T06:14:46.350753Z",
     "start_time": "2025-06-26T06:14:46.346738Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['c:\\\\Program Files\\\\Python312\\\\python312.zip', 'c:\\\\Program Files\\\\Python312\\\\DLLs', 'c:\\\\Program Files\\\\Python312\\\\Lib', 'c:\\\\Program Files\\\\Python312', '', 'C:\\\\Users\\\\41507\\\\AppData\\\\Roaming\\\\Python\\\\Python312\\\\site-packages', 'C:\\\\Users\\\\41507\\\\AppData\\\\Roaming\\\\Python\\\\Python312\\\\site-packages\\\\win32', 'C:\\\\Users\\\\41507\\\\AppData\\\\Roaming\\\\Python\\\\Python312\\\\site-packages\\\\win32\\\\lib', 'C:\\\\Users\\\\41507\\\\AppData\\\\Roaming\\\\Python\\\\Python312\\\\site-packages\\\\Pythonwin', 'c:\\\\Program Files\\\\Python312\\\\Lib\\\\site-packages']\n"
     ]
    }
   ],
   "source": [
    "import sys\n",
    "print(sys.path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-26T06:14:48.340332Z",
     "start_time": "2025-06-26T06:14:46.350753Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.datasets import fetch_california_housing\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "from tqdm.auto import tqdm\n",
    "from torch.utils.data import DataLoader, Dataset\n",
    "from deeplearning_func import EarlyStopping, ModelSaver\n",
    "from deeplearning_func import plot_learning_curves,plot_learning_loss_curves\n",
    "import os"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-26T06:14:48.358208Z",
     "start_time": "2025-06-26T06:14:48.340332Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集大小: 13209\n",
      "验证集大小: 3303\n",
      "测试集大小: 4128\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "# 加载加利福尼亚房价数据集\n",
    "housing = fetch_california_housing()\n",
    "X = housing.data\n",
    "y = housing.target\n",
    "\n",
    "# 数据拆分：训练集(60%)、验证集(20%)、测试集(20%)\n",
    "X_train_val, X_test, y_train_val, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
    "X_train, X_val, y_train, y_val = train_test_split(X_train_val, y_train_val, test_size=0.2, random_state=42)  # 0.25 x 0.8 = 0.2\n",
    "\n",
    "print(f\"训练集大小: {X_train.shape[0]}\")\n",
    "print(f\"验证集大小: {X_val.shape[0]}\")\n",
    "print(f\"测试集大小: {X_test.shape[0]}\")\n",
    "\n",
    "# 数据标准化\n",
    "scaler = StandardScaler()\n",
    "X_train_scaled = scaler.fit_transform(X_train)\n",
    "X_val_scaled = scaler.transform(X_val)\n",
    "X_test_scaled = scaler.transform(X_test)\n",
    "\n",
    "# 自定义数据集类\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, features, targets):\n",
    "        self.features = torch.FloatTensor(features)\n",
    "        self.targets = torch.FloatTensor(targets).view(-1, 1)\n",
    "        \n",
    "    def __len__(self):\n",
    "        return len(self.features) #返回样本数量\n",
    "    \n",
    "    def __getitem__(self, idx): #传入索引，返回对应索引样本的特征和目标\n",
    "        return self.features[idx], self.targets[idx]\n",
    "\n",
    "# 创建数据集实例\n",
    "train_dataset = HousingDataset(X_train_scaled, y_train)\n",
    "val_dataset = HousingDataset(X_val_scaled, y_val)\n",
    "test_dataset = HousingDataset(X_test_scaled, y_test)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 加载数据，构建模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-26T06:14:49.085821Z",
     "start_time": "2025-06-26T06:14:48.358208Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "RegressionModel(\n",
      "  (layer1): CustomLinear()\n",
      "  (activation): ReLU()\n",
      "  (output): CustomLinear()\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "from torch import optim\n",
    "import math\n",
    "\n",
    "# 创建数据加载器\n",
    "train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\n",
    "val_loader = DataLoader(val_dataset, batch_size=32)\n",
    "test_loader = DataLoader(test_dataset, batch_size=32)\n",
    "\n",
    "# 自定义线性层\n",
    "class CustomLinear(nn.Module):\n",
    "    def __init__(self, in_features, out_features, bias=True):\n",
    "        super(CustomLinear, self).__init__()\n",
    "        self.in_features = in_features\n",
    "        self.out_features = out_features\n",
    "        \n",
    "        # 初始化权重和偏置参数\n",
    "        self.weight = nn.Parameter(torch.Tensor(out_features, in_features))\n",
    "        if bias:\n",
    "            self.bias = nn.Parameter(torch.Tensor(out_features))\n",
    "        else:\n",
    "            self.register_parameter('bias', None)\n",
    "        \n",
    "        # 初始化参数\n",
    "        self.reset_parameters()\n",
    "        \n",
    "    def reset_parameters(self):\n",
    "        # 使用与nn.Linear相同的初始化方法\n",
    "        nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))\n",
    "        if self.bias is not None:\n",
    "            fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight)\n",
    "            bound = 1 / math.sqrt(fan_in)\n",
    "            nn.init.uniform_(self.bias, -bound, bound)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        # 实现线性变换：y = xW^T + b\n",
    "        output = torch.matmul(x, self.weight.t())\n",
    "        if self.bias is not None:\n",
    "            output += self.bias\n",
    "        return output\n",
    "\n",
    "# 定义神经网络模型\n",
    "class RegressionModel(nn.Module):\n",
    "    def __init__(self, input_dim):\n",
    "        super(RegressionModel, self).__init__()\n",
    "        self.layer1 = CustomLinear(input_dim, 30)\n",
    "        self.activation = nn.ReLU()\n",
    "        self.output = CustomLinear(30, 1)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        x = self.activation(self.layer1(x))\n",
    "        x = self.output(x)\n",
    "        return x\n",
    "\n",
    "# 初始化模型、损失函数和优化器\n",
    "input_dim = X_train.shape[1]\n",
    "model = RegressionModel(input_dim)\n",
    "criterion = nn.MSELoss()\n",
    "optimizer = optim.Adam(model.parameters())\n",
    "\n",
    "# 打印模型结构\n",
    "print(model)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-26T06:14:49.089336Z",
     "start_time": "2025-06-26T06:14:49.086826Z"
    }
   },
   "outputs": [],
   "source": [
    "# 评估模型\n",
    "def evaluate_regression_model(model, dataloader,  device,criterion):\n",
    "    model.eval()\n",
    "    running_loss = 0.0\n",
    "\n",
    "    \n",
    "    with torch.no_grad():#禁止 autograd 记录计算图，节省显存与算力。\n",
    "        for inputs, targets in dataloader:\n",
    "            inputs, targets = inputs.to(device), targets.to(device)\n",
    "            outputs = model(inputs) #前向计算\n",
    "            loss = criterion(outputs, targets) #计算损失\n",
    "            \n",
    "            running_loss += loss.item() * inputs.size(0)\n",
    "    \n",
    "    return running_loss / len(dataloader.dataset)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-26T06:15:20.191876Z",
     "start_time": "2025-06-26T06:14:49.089839Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5fb44834df9e45acae246bc4ae6f7287",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "train progress:   0%|          | 0/41300 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "早停触发! 最佳验证准确率(如果是回归，这里是损失): 0.3466\n",
      "早停: 已有10轮验证损失没有改善！\n"
     ]
    }
   ],
   "source": [
    "# 定义回归模型训练函数\n",
    "def train_regression_model(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    criterion, \n",
    "    optimizer, \n",
    "    device='cpu', \n",
    "    num_epochs=100, \n",
    "    print_every=10,\n",
    "    eval_step=500,\n",
    "    model_saver=None,\n",
    "    early_stopping=None\n",
    "):\n",
    "    \"\"\"\n",
    "    训练回归模型的函数\n",
    "    \n",
    "    参数:\n",
    "        model: 要训练的模型\n",
    "        train_loader: 训练数据加载器\n",
    "        val_loader: 验证数据加载器\n",
    "        criterion: 损失函数\n",
    "        optimizer: 优化器\n",
    "        device: 训练设备\n",
    "        num_epochs: 训练轮次\n",
    "        print_every: 每多少轮打印一次结果\n",
    "        eval_step: 每多少步评估一次\n",
    "    \n",
    "    返回:\n",
    "        record_dict: 包含训练和验证记录的字典\n",
    "    \"\"\"\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    epoch_val_loss=0\n",
    "    \n",
    "    with tqdm(total=num_epochs * len(train_loader), desc=\"train progress\") as pbar:\n",
    "        for epoch_id in range(num_epochs):\n",
    "            # 训练\n",
    "            model.train()\n",
    "            running_loss = 0.0\n",
    "            \n",
    "            for inputs, targets in train_loader:\n",
    "                inputs, targets = inputs.to(device), targets.to(device)\n",
    "                \n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                \n",
    "                # 模型前向计算\n",
    "                outputs = model(inputs)\n",
    "                \n",
    "                # 计算损失\n",
    "                loss = criterion(outputs, targets)\n",
    "                \n",
    "                # 梯度回传，计算梯度\n",
    "                loss.backward()\n",
    "                \n",
    "                # 更新模型参数\n",
    "                optimizer.step()\n",
    "                \n",
    "                # 更新步骤\n",
    "                global_step += 1\n",
    "            \n",
    "                # 在每个批次后记录训练损失\n",
    "                epoch_train_loss = loss.item()\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": epoch_train_loss,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # 验证\n",
    "                if global_step % eval_step == 0:\n",
    "                    epoch_val_loss = evaluate_regression_model(model, val_loader, device, criterion)\n",
    "            \n",
    "                    # 记录验证数据\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": epoch_val_loss, \n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    # 保存模型权重\n",
    "                    # 如果有模型保存器，保存模型\n",
    "                    if model_saver is not None:\n",
    "                        model_saver(model, -epoch_val_loss, epoch_id)\n",
    "                    \n",
    "                    # 如果有早停器，检查是否应该早停\n",
    "                    if early_stopping is not None:\n",
    "                        early_stopping(-epoch_val_loss)\n",
    "                        if early_stopping.early_stop:\n",
    "                            print(f'早停: 已有{early_stopping.patience}轮验证损失没有改善！')\n",
    "                            return model,record_dict\n",
    "            \n",
    "                # 更新进度条\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"loss\": f\"{epoch_train_loss:.4f}\", \"val_loss\": f\"{epoch_val_loss:.4f},global_step{global_step}\"})\n",
    "    \n",
    "    return model, record_dict\n",
    "\n",
    "\n",
    "# 训练模型\n",
    "# 初始化早停和模型保存对象\n",
    "early_stopping = EarlyStopping(patience=10, verbose=True)\n",
    "model_saver = ModelSaver(save_dir='model_weights')\n",
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "model, record_dict = train_regression_model(\n",
    "    model=model,\n",
    "    train_loader=train_loader,\n",
    "    val_loader=val_loader,\n",
    "    criterion=criterion,\n",
    "    optimizer=optimizer,\n",
    "    num_epochs=100,\n",
    "    print_every=10,\n",
    "    eval_step=500,\n",
    "    early_stopping=early_stopping,\n",
    "    model_saver=model_saver,\n",
    "    device=device\n",
    ")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-26T06:15:20.355745Z",
     "start_time": "2025-06-26T06:15:20.192889Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画图\n",
    "plot_learning_loss_curves(record_dict,sample_step=500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-26T06:15:20.412545Z",
     "start_time": "2025-06-26T06:15:20.356836Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "测试集上的损失为0.3282\n"
     ]
    }
   ],
   "source": [
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "test_loss=evaluate_regression_model(model,test_loader,device,criterion)\n",
    "print(f\"测试集上的损失为{test_loss:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
